/*
 * Copyright 2009-2010 the original author or authors.
 *
 * Licensed under the Apache license, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.internna.iwebjtracker.server;

import org.internna.iwebjtracker.utils.UrlUtils;
import org.internna.iwebjtracker.security.Credentials;
import org.internna.iwebjtracker.server.connection.ServerConnection;
import org.internna.iwebjtracker.server.connection.ConnectionDetails;

import static org.internna.iwebmvc.utils.StringUtils.hasText;

/**
 * Convenience implementation of the common connection functionality.
 *
 * @author Jose Noheda
 * @since 1.0
 */
public abstract class AbstractConnectionManagerServer<T extends ServerConnection, C extends ConnectionDetails> extends AbstractProtocolManagerServer<T, C> {

    /**
     * Checks that the host is valid. Valid hosts include localhost, a valid DNS name
     * or an IP.
     *
     * @param host any string
     * @return true if the host string is valid network address
     */
    protected final boolean validateConnectionHost(String host) {
        return UrlUtils.isValidHost(host);
    }

    /**
     * Checks positive port values.
     *
     * @param port any integer
     * @return true if the parameter is greater than zero.
     */
    protected boolean validateConnectionPort(int port) {
        return port > 0;
    }

    /**
     * Checks the path. Accepts everything that starts with a slash (or null)
     *
     * @param path any
     * @return true unless the path does no start with a slash
     */
    protected boolean validateConnectionServerPath(String path) {
        return hasText(path) ? path.startsWith("/") : true;
    }

    /**
     * Convenience implementation (override as required)
     *
     * @param credentials any
     * @return true
     */
    protected boolean validateConnectionCredentials(Credentials credentials) {
        return true;
    }

    /**
     * Validates that the connection data is coherent.
     *
     * @return true if all the required data is there.
     */
    protected final boolean validateConnection(C connectionDetails) {
        boolean validate = connectionDetails != null;
        if (validate) {
            validate &= validateProtocol(connectionDetails.getProtocol());
            validate &= validateConnectionPort(connectionDetails.getPort());
            validate &= validateConnectionHost(connectionDetails.getHost());
            validate &= validateConnectionCredentials(connectionDetails.getCredentials());
            validate &= validateConnectionServerPath(connectionDetails.getServerPath());
        }   
        return validate;
    }

    /**
     * Validates connection data and if everything went ok continues with the test.
     */
    @Override public final boolean testConnection(C connectionDetails) {
        return validateConnection(connectionDetails) ? obtainConnection(connectionDetails) : false;
    }

    private boolean obtainConnection(C connectionDetails) {
        T connection = openConnection(connectionDetails);
        boolean opened = connection != null;
        if (opened) {
            opened = connection.isOpen() && doTestConnection(connection);
            closeConnection(connection);
            opened &= !connection.isOpen();
        }
        return opened;
    }

    /**
     * By default a connection is tested by opening and closing it. Some servers may need to actually
     * operate to check the connection has been physically created.
     */
    protected boolean doTestConnection(T connection) {
        return true;
    }

    /**
     * Validates the connection data and if everything went ok relies on the plugin to actually open a physical connection.
     * @return null if the connection was not valid. Otherwise whatever the implementation returns.
     */
    @Override public final T openConnection(C connectionDetails) {
        return validateConnection(connectionDetails) ? doOpenConnection(connectionDetails) : null;
    }

    /**
     * Validates the connection data and if everything went ok relies on the plugin to actually close the physical connection.
     */
    @Override public final void closeConnection(T connection) {
        if ((connection != null) && connection.isClosable() && connection.isOpen()) {
            doCloseConnection(connection);
        }
    }

    /**
     * Implemented by plug-ins to actually open a physical connection.
     *
     * @return true null or an open connection
     */
    protected abstract T doOpenConnection(C connectionDetails);

    /**
     * Implemented by plug-ins to actually close a physical connection.
     * 
     * @param connection a non null valid open connection
     */
    protected abstract void doCloseConnection(T connection);

}
